<?php

/**
 * ACL plugin
 *
 * This plugin provides ACL functionality
 * 
   * Copyright © 2010 Jack Cleaver
   * 
   * This plugin provides functionality added by CalDAV (RFC 4791)
   * It implements new reports, and the MKCALENDAR method.
   *
   * This file is part of Slash.
   *
   * Slash is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * Slash is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   *
   * You should have received a copy of the GNU General Public License
   * along with Slash.  If not, see <http://www.gnu.org/licenses/>.
 */
class Slash_ACL_Plugin extends Sabre_DAV_ServerPlugin
{

	/**
	 * Reference to server object
	 *
	 * @var Sabre_DAV_Server
	 */
	public $server;

	private $defaultAcl;

	public $authBackend;
	
	private $authRealm;
	
	private $aclRestrictions;

	private static $instance;

	private $propfindProps = array(
          '{DAV:}getlastmodified',
          '{DAV:}getcontentlength',
          '{DAV:}resourcetype',
          '{DAV:}quota-used-bytes',
          '{DAV:}quota-available-bytes',
          '{DAV:}getetag',
          '{DAV:}getcontenttype',
	);

	/*
	 * This is the supported-privilege-set
	 */
	private $privileges = array(
		"all" => array(
			"abstract"=>false,
			"description"=>"Any operation",
			"container" => array(
				"read" => array(
					"abstract"=>false,
					"description"=>"Read content, ACL and properties",
					"container" => array(	
						"read-content" => array(
							"abstract"=>true,
							"description"=>"Read content",
	),
						"read-properties" => array(
							"abstract"=>true,
							"description"=>"Read properties",
	),
						"read-acl" => array(
							"abstract"=>true,
							"description"=>"Read ACL",
	),
	),
	),
				"write" => array(
					"abstract"=>false,
					"description"=>"Write content, ACL and properties, create and destroy resources",
					"container" => array(	
						"write-content" => array(
							"abstract"=>true,
							"description"=>"Write content",
	),
						"write-properties" => array(
							"abstract"=>true,
							"description"=>"Write properties",
	),
						"write-acl" => array(
							"abstract"=>true,
							"description"=>"Write ACL",
	),
						"bind" => array(
							"abstract"=>true,
							"description"=>"Create a resource",
	),
						"unbind" => array(
							"abstract"=>true,
							"description"=>"Destroy a resource",
	),
	),
	),
				"unlock" => array(
					"abstract"=>false,
					"description"=>"Unlock a resource locked by another principal",
	),
	),
	)
	);

	public static function getInstance()
	{
		if (!self::$instance)
		{
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function initialize(Sabre_DAV_Server $server)
	{
		$this->server = $server;
		//Slash_Debug::debug("\nHandling Test: " . $server->httpRequest->getHeader("X-Daydream-Test"));
		$server->subscribeEvent('beforeMethod', array($this, 'beforeMethod'), 10);
		$server->subscribeEvent('beforeUpdateProperties', array($this, 'beforeUpdateProperties'), 10);
		$server->subscribeEvent('unknownMethod', array($this, 'unknownMethod'));
		$server->subscribeEvent('afterBind', array($this, 'afterBind'));
		$server->subscribeEvent('afterGetProperties', array($this, 'afterGetProperties'));
		$server->subscribeEvent('report', array($this, 'report'));

		//array_push($server->protectedProperties, "{DAV:}owner");
		array_push($server->protectedProperties, "{DAV:}acl");
		array_push($server->protectedProperties, "{DAV:}acl-restrictions");
		array_push($server->protectedProperties, "{DAV:}supported-privilege-set");
		//unset($server->protectedProperties["{DAV:}group-membership"]);

		$server->propertyMap['{DAV:}acl'] = 'Slash_ACL_Property_Acl';
		$server->propertyMap['{DAV:}group-membership'] = 'Slash_ACL_Property_GroupMembership';
		$server->propertyMap['{DAV:}principal'] = 'Slash_ACL_Property_Principal';
		$server->propertyMap['{DAV:}group-member-set'] = 'Slash_ACL_Property_GroupMemberSet';
		$server->propertyMap['{DAV:}acl-restrictions'] = 'Slash_ACL_Property_AclRestrictions';

	}

	public function configure($server, $config, $authBackend)
	{
		Slash_Debug::debug("Configuring ACL Plugin");
		//$this->initialize($server);
		$this->defaultAcl = $config->getDefaultAcl();
		$this->aclRestrictions = $config->getAclRestrictions();
		//$this->authBackend = $authBackend;
		$this->authRealm = $config->getAuthRealm();

		$rootDirectory = $server->tree->getNodeForPath("/");
		$principalRoot = $config->getPrincipalRoot();

		//We have to have a Principal root, an admin user and an admins group.
		try
		{
			$this->principalRoot = $rootDirectory->getChild($principalRoot);
		}
		catch (Sabre_DAV_Exception_FileNotFound $e)
		{
			Slash_Debug::debug("No principal root collection found, creating");
			try
			{
				$rootDirectory->createDirectory($config->getPrincipalRoot());
				$this->principalRoot = $rootDirectory->getChild($principalRoot);

				$principalsAcl = new Slash_ACL_Property_Acl("Principals ACL");
				$princ = new Slash_ACL_Property_PropertyPrincipal("DAV:", "owner");
				$principalsAcl->grant($princ, array(0=>"all"), true);

				$princ = new Slash_ACL_Property_HrefPrincipal("/principals/admins");
				$principalsAcl->grant($princ, array(0=>"all"), true);
				$owner = new Slash_ACL_Property_Owner(new Slash_ACL_Property_Href($princ->getHref()));

				$propreq = array();
				$propreq["{DAV:}acl"] = $principalsAcl;
				$propreq["{DAV:}owner"] = $owner;
				Slash_Debug::debug("Setting ACL and Owner on principals collection");
				$this->principalRoot->updateProperties($propreq);

				Slash_Debug::debug("Creating admin user principal");
				$this->principalRoot->createDirectory("admin");
				Slash_Debug::debug("Creating admins group principal");
				$this->principalRoot->createDirectory("admins");

				$admin = $this->principalRoot->getChild("admin");
				$password = $config->getDefaultAdminPassword();
				$propreq = array();
				$propreq["{http://jackpot.uk.net/slash}password"] = $password;
				Slash_Debug::debug("Setting password '$password' on admin user principal");
				$result = $admin->updateProperties($propreq);
				Slash_Debug::debugDump("Set password '$password' on admin user principal: ", $result);

				$admins = $this->principalRoot->getChild("admins");
				$href = new Slash_ACL_Property_Href("/principals/admin");
				$gms = new Slash_ACL_Property_GroupMemberSet(array($href));
				$propreq = array("{DAV:}group-member-set" => $gms);
				Slash_Debug::debug("Adding admin to group-member-set of admins group");
				$admins->updateProperties($propreq);
			}
			catch (Exception $e)
			{
				Slash_Debug::debug("Exception configuring principal root: " . $e->getMessage());
			}
		}
		$this->authBackend = new $authBackend($config);
	}


	public function getHTTPMethods($uri)
	{
		return array('ACL');
	}

	/**
	 * This functions handles REPORT requests specific to CalDAV
	 *
	 * @param string $reportName
	 * @param DOMNode $dom
	 * @return bool
	 */
	public function report($reportName, $dom)
	{
		switch($reportName)
		{
			case '{DAV:}acl-principal-prop-set' :
				$this->principalPropSetReport($dom);
				return false;

			case '{DAV:}principal-match' :
				$this->principalMatchReport($dom);
				return false;

			case '{DAV:}expand-property' :
				$this->expandPropertyReport($dom);
				return false;
					
			case '{DAV:}principal-property-search' :
				if ($this->server->getRequestUri()==='principals')
				{
					$this->principalPropertySearchReport($dom);
					return false;
				}
				break;
					
			case '{DAV:}principal-search-property-set' :
				if ($this->server->getRequestUri()==='principals')
				{
					$this->principalSearchPropertySetReport($dom);
					return false;
				}
				break;
					
			default :
				return true;
		}
	}

	public function getFeatures()
	{

		return array('access-control');

	}

	public function containsPrivilege($container, $priv)
	{
		$result = false;
		//Slash_Debug::debugDump("containsPrivilege $priv: container:", $container);
		foreach ($container as $privName=>$inner)
		{
			if ($privName == $priv)
			{
				$result = true;
			} else {
				if (isset($inner["container"]))
				{
					$container = $inner["container"];
					$result = $this->containsPrivilege($container, $priv);
				}
			}
			if ($result)
			{
				break;
			}
		}
		//Slash_Debug::debug("$container contains $priv2: $result");
		return $result;
	}

	public function findPrivilege($container, $priv)
	{
		$result = null;
		Slash_Debug::debug("findPrivilege: $priv");
		foreach ($container as $outer=>$inner)
		{
			if ($outer == $priv)
			{
				Slash_Debug::debug("findPrivilege: found $priv");
				$result = $inner;
			} else {
				if (isset($inner["container"]))
				{
					$container = $inner["container"];
					$result = $this->findPrivilege($container, $priv);
				}
			}
			if ($result)
			{
				break;
			}
		}
		//Slash_Debug::debug("$container contains $priv2: $result");
		return $result;
	}

	/*
	 * Return true if $priv1 includes $priv2.
	 * Method:
	 */
	public function includesPrivilege($priv1, $priv2)
	{
		Slash_Debug::debug("ACL_Plugin includesPrivilege($priv1, $priv2)");
		$result = false;
		if ($priv1 == $priv2)
		{
			$result = true;
		} else {
			$inner = $this->findPrivilege($this->privileges, $priv1);
			if (isset($inner["container"]))
			{
				//Slash_Debug::debugDump("ACL_Plugin includesPrivilege: found privilege $priv1, container=:", $container);
				$result = $this->containsPrivilege($inner["container"], $priv2);
			}
		}
		Slash_Debug::debug("ACL_Plugin includesPrivilege: $priv1 includes $priv2: " . $result?"true":"false");
		return $result;
	}

	/*
	 * See if the HREF principal with the URI $href has the privilege $priv.
	 *
	 * see if this ACL grants the specified privilege to the specified principal
	 * with respect to the specified node.
	 * <principal>
	 * 	<property>
	 * 		<owner />
	 * 	</property>
	 * </principal>
	 * <grant>
	 *  ...privs...
	 * </grant>
	 * Method:
	 *  For each privilege,
	 *  	If that privilege includes $priv
	 *			Say "yes" and bail out.
	 *  Say "No".
	 */
	public function grantsPrivilege(Slash_ACL_Property_Acl $acl, Sabre_DAV_FSExt_Node $node, Slash_ACL_Property_Principal $principal, $priv)
	{
		//return true;
		$result = false;
		$aces = $acl->getAces();
		//Slash_Debug::debugDump("ACL_Plugin $acl->name grantsPrivilege:", $aces);
		$princStr = $principal->toString();
		foreach ($aces as $grantPrincStr=>$princAces)
		{
			$name = $node->getName();
			Slash_Debug::debug("ACL_Plugin grantsPrivilege ACLName=$acl->name for node $name: ace Principal is $grantPrincStr, target Principal is " . $principal->toString());
			if (!$acl->isValidPrincStr($grantPrincStr))
			{
				Slash_Debug::debug("ACL_Plugin grantsPrivilege ACLName=$acl->name for $name: INVALID $grantPrincStr");
				continue;
			}
			$grantPrinc = Slash_ACL_Property_Principal::fromString($grantPrincStr);
			Slash_Debug::debug("ACL_Plugin grantsPrivilege ACLName=$acl->name: grantPrincStr=$grantPrincStr");
			if ($grantPrinc->includesPrincipal($principal, $node))
			{
				Slash_Debug::debug("ACL_Plugin grantsPrivilege ACLName=$acl->name: $grantPrincStr includes " . $principal->toString());
				foreach ($princAces as $princAce)
				{
					$privs = $princAce["privs"];
					//Slash_Debug::debugDump("ACL_Plugin grantsPrivilege $acl->name: found privileges:", $privs);
					if (is_array($privs))
					{
						foreach ($privs as $acePriv)
						{
							if ($this->includesPrivilege($acePriv, $priv))
							{
								//return true;
								return $princAce["grant"];
							}
						}
					} else {
						Slash_Debug::debug("ACL_Plugin grantsPrivilege ACLName=$acl->name: privs for $grantPrincStr is not an array, granting");
						return true;
					}
				}
			} else {
				Slash_Debug::debug("ACL_Plugin grantsPrivilege ACLName=$acl->name: $grantPrincStr DOES NOT include " . $principal->toString());
			}
		}
		//return true;
		return $result;
	}

	private function hasPrivilege(Slash_ACL_Property_Principal $principal, $node, $privilege)
	{
		Slash_Debug::debug("ACL_Plugin_hasPrivilege: principal=" . $principal->toString());
		$result = false;
		$propreq = array("{DAV:}acl");
		$props = $node->getProperties($propreq);
		if (isset($props["{DAV:}acl"]))
		{
			$acl = $props["{DAV:}acl"];
			$nodeName = $node->getName();
			Slash_Debug::debugDump("ACL_Plugin_hasPrivilege: ACL found for node $nodeName:", $acl);
			$result = ($this->grantsPrivilege($acl, $node, $principal, $privilege));
		} else {
			Slash_Debug::debug("ACL_Plugin_hasPrivilege: no ACL found, granting all");
			$result = true;
		}
		return $result;
	}

	/*
	 * Return an array of privileges that apply to the specified principal. The
	 * array is of the form
	 * array (
	 * 	privname=>array()
	 * )
	 * Where the inner array contains the non-abstract contained privileges.
	 */
	private function getPrivileges(Slash_ACL_Property_Principal $principal, $node)
	{
		Slash_Debug::debug("ACL_Plugin_getPrivileges: principal=" . $principal->toString());
		$result = array();
		$propreq = array("{DAV:}acl");
		$props = $node->getProperties($propreq);
		if (isset($props["{DAV:}acl"]))
		{
			$acl = $props["{DAV:}acl"];
			$nodeName = $node->getName();
			Slash_Debug::debugDump("ACL_Plugin_getPrivileges ACLName=$acl->name: ACL found for node $nodeName:", $acl);
			$aces = $acl->getAces();
			foreach ($aces as $grantPrincStr=>$princAce)
			{
				Slash_Debug::debug("ACL_Plugin getPrivileges: grantPrincStr=$grantPrincStr");
				$grantPrinc = Slash_ACL_Property_Principal::fromString($grantPrincStr);
				if ($grantPrinc->includesPrincipal($principal, $node))
				{
					$privs = $princAce["privs"];
					foreach ($privs as $priv)
					{
						$container = $this->findPrivilege($this->privileges, $priv);
						$result[$priv] = $container;
					}
				}
			}
		} else {
			Slash_Debug::debug("ACL_Plugin_getPrivileges: no ACL found, granting all");
			$result = true;
		}
		return $result;
	}


	private function getOwner($node)
	{
		$propreq = array("{DAV:}owner");
		$props = $node->getProperties($propreq);
		return $props["{DAV:}owner"];
	}

	/*
	 * Check that the current user has the required privs.
	 * See http://tools.ietf.org/html/rfc3744#section-7
	 */
	public function beforeUpdateProperties($uri, $properties, $result)
	{
		Slash_Debug::debugDump("ACL_Plugin beforeUpdateProperties: ", $properties);
		if (isset($properties["{DAV:}owner"]))
		{
			//Only the owner and admins may change the owner.
			$node = $this->server->tree->getNodeForPath($uri);
			$owner = $this->getOwner($node);
			if ($owner && $owner instanceof Sabre_DAV_Properties_IHref)
			{
				$user = $this->server->getCurrentUserPrincipal();
				$uriNode = new Slash_ACL_FSExt_UriNode($user);
				$strUser = $uriNode->getUri();
				$userProp = new Slash_ACL_Property_HrefPrincipal($strUser);
				$admins = new Slash_ACL_Property_HrefPrincipal("/principals/admins");
				$strOwner = $owner->getHref();
				Slash_Debug::debug("ACL_Plugin beforeUpdateProperties: owner=$strOwner user=$strUser");
				if ($strOwner != $strUser && !$admins->includesPrincipal($userProp))
				{
					$result[403]["{DAV:}owner"] = null;
					unset($properties["{DAV:}owner"]);
					foreach($properties as $propName=>$value)
					{
						$result[424][$propName] = null;
					}
					$result['href'] = $uri;
					return false;
				}
			}
		}
		return true;
	}


	/*
	 * Check that the current user has the required privs.
	 * See http://tools.ietf.org/html/rfc3744#section-7
	 */
	public function beforeMethod($method)
	{
		//$method = $amethod[0];
		Slash_Debug::dumpRequest($this->server->httpRequest);
		$uri = $this->server->getRequestUri();
		Slash_Debug::debug("ACL Plugin beforeMethod: $method for $uri");
		$authUser = $this->server->httpRequest->getRawServerValue('PHP_AUTH_USER');
		$authPass = $this->server->httpRequest->getRawServerValue('PHP_AUTH_PW');
		if (isset($authUser) && isset($authPass))
		{
			$authOk = $this->authBackend->authenticate($this->server, $this->authRealm);
			Slash_Debug::debug("ACL Plugin beforeMethod: authenticate returned " . ($authOk?"true":"false"));
		}
		$user = $this->authBackend->getCurrentUser();
		if ($user)
		{
			//Slash_Debug::debug("ACL Plugin beforeMethod: got a user: " . $user["uri"]);
			$userPrinc = new Slash_ACL_Property_HrefPrincipal($user["uri"]);
		} else {
			$userPrinc = new Slash_ACL_Property_UnauthenticatedPrincipal();
		}
		Slash_Debug::debug("ACL Plugin beforeMethod: got a user Principal property: " . $userPrinc->toString());
		$resources = array();
		switch ($method)
		{
			case "GET":
				$node = $this->server->tree->getNodeForPath($uri);
				Slash_Debug::debug("Slash ACL Plugin beforeMethod: Classname of node:" . get_class($node));
				if (!$this->hasPrivilege($userPrinc, $node, "read"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"read");
				}
				break;
			case "HEAD":
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "read"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"read");
				}
				break;
			case "OPTIONS":
				/*
				 $node = $this->server->tree->getNodeForPath($uri);
				 if (!$node->hasPrivilege($userPrinc, "read"))
				 {
				 $resources[] = array("href"=>$uri, "privilege"=>"read");
				 }
				 */
				break;
			case "PUT":
				Slash_Debug::debug("ACL Plugin beforeMethod PUT: uri=$uri");
				try
				{
					$node = $this->server->tree->getNodeForPath($uri);
					if (!$this->hasPrivilege($userPrinc, $node, "write-content"))
					{
						$resources[] = array("href"=>$uri, "privilege"=>"write-content");
					}
				}
				catch (Sabre_DAV_Exception_FileNotFound $e)
				{
					list($parentUri,$newName) = Sabre_DAV_URLUtil::splitPath($uri);
					$parentNode = $this->server->tree->getNodeForPath($parentUri);
					if (!$this->hasPrivilege($userPrinc, $parentNode, "bind"))
					{
						$resources[] = array("href"=>$parentUri, "privilege"=>"bind");
					}
				}
				break;
			case "PROPPATCH":
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "write-properties"))
				{
					Slash_Debug::debug("ACL_Plugin beforeMethod PROPPATCH: user lacks write-properties");
					$resources[] = array("href"=>$uri, "privilege"=>"write-properties");
				}
				break;
			case "ACL":
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "write-acl"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"write-acl");
				}
				break;
			case "PROPFIND":
				//TODO: need special handling for acl and current-user-privilege-set properties
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "read"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"read");
				} else {
					$body = $this->server->httpRequest->getBody(true);
					//$body2 = $this->server->httpRequest->getBody(true);
					$this->server->httpRequest->setBody($body, true);
					$this->propfindProps = $this->server->parsePropfindRequest($body);
					//Slash_Debug::debug("ACL_Plugin beforeMethod PROPFIND: first body = $body; second body=$body2");
				}
				break;
			case "COPY":
				$info = $this->server->getCopyAndMoveInfo();
				if ($info['destinationExists'])
				{
					$node = $this->server->tree->getNodeForPath($uri);
					$destNode = $this->server->tree->getNodeForPath($info['destination']);
					if (!$this->hasPrivilege($userPrinc, $node, "read"))
					{
						$resources[] = array("href"=>$uri, "privilege"=>"read");
					}
					if (!$this->hasPrivilege($userPrinc, $destNode, "write-content"))
					{
						$resources[] = array("href"=>$info["destination"], "privilege"=>"write-content");
					}
					if (!$this->hasPrivilege($userPrinc, $destNode, "write-properties"))
					{
						$resources[] = array("href"=>$info["destination"], "privilege"=>"write-properties");
					}
				} else {
					$node = $this->server->tree->getNodeForPath($uri);
					$destNode = $this->server->tree->getNodeForPath($info['destination']);
					if (!$this->hasPrivilege($userPrinc, $node, "read"))
					{
						$resources[] = array("href"=>$uri, "privilege"=>"read");
					}
					if (!$this->hasPrivilege($userPrinc, $destNode, "bind"))
					{
						$resources[] = array("href"=>$info["destination"], "privilege"=>"bind");
					}
				}
				break;
			case "MOVE":
				$info = $this->server->getCopyAndMoveInfo();
				$node = $this->server->tree->getNodeForPath($uri);
				$destNode = $this->server->tree->getNodeForPath($info['destination']);
				if (!$this->hasPrivilege($userPrinc, $node, "unbind"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"unbind");
				}
				if (!$this->hasPrivilege($userPrinc, $destNode, "bind"))
				{
					$resources[] = array("href"=>$info["destination"], "privilege"=>"bind");
				}
				if ($info['destinationExists'])
				{
					if (!$this->hasPrivilege($userPrinc, $destNode, "unbind"))
					{
						$resources[] = array("href"=>$info["destination"], "privilege"=>"unbind");
					}
				}
				break;
			case "DELETE":
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "unbind"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"unbind");
				}
				break;
			case "LOCK":
				try
				{
					$node = $this->server->tree->getNodeForPath($uri);
					if (!$this->hasPrivilege($userPrinc, $node, "write-content"))
					{
						$resources[] = array("href"=>$uri, "privilege"=>"unbind");
					}
				}
				catch (Sabre_DAV_Exception_FileNotFound $e)
				{
					list($parentUri,$newName) = Sabre_DAV_URLUtil::splitPath($uri);
					$parentNode = $this->server->tree->getNodeForPath($parentUri);
					if (!$this->hasPrivilege($userPrinc, $parentNode, "bind"))
					{
						$resources[] = array("href"=>$parentUri, "privilege"=>"bind");
					}
				}
				break;
			case "MKCOL":
				list($parentUri,$newName) = Sabre_DAV_URLUtil::splitPath($uri);
				$parentNode = $this->server->tree->getNodeForPath($parentUri);
				if (!$this->hasPrivilege($userPrinc, $parentNode, "bind"))
				{
					$resources[] = array("href"=>$parentUri, "privilege"=>"bind");
				}
				break;
			case "UNLOCK":
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "unlock"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"unlock");
				}
				break;
			case "REPORT":
				//TODO: user actually requires read on all referenced resources.
				$node = $this->server->tree->getNodeForPath($uri);
				if (!$this->hasPrivilege($userPrinc, $node, "read"))
				{
					$resources[] = array("href"=>$uri, "privilege"=>"read");
				}
				break;
			default:
				break;
		}
		if (count($resources) > 0)
		{
			Slash_Debug::debugDump("ACL_Plugin beforeMethod: need privileges: ", $resources);
			if ($userPrinc->toString() == "unauthenticated:")
			{
				Slash_Debug::debug("ACL_Plugin beforeMethod: Calling backend authenticate()");
				$this->authBackend->authenticate($this->server, $this->authRealm);
			} else {
				throw new Slash_ACL_Exception_NeedPrivileges($resources);
			}
		} else {
			Slash_Debug::debug("ACL_Plugin beforeMethod: all required privs are present");
		}
		return true;
	}

	/**
	 * This function handles support for the ACL method
	 *
	 * @param string $method
	 * @return bool
	 */
	public function unknownMethod($method)
	{
		$result = true;
		if ($method == 'ACL')
		{
			$this->httpAcl();
			$result = false;
		}
		// false is returned to stop the unknownMethod event
		return $result;

	}

	public function afterBind($uri)
	{
		Slash_Debug::debug("ACL Plugin afterBind");
		$propreq = array();
		
		if (isset($_SERVER["CONTENT_TYPE"]))
		{
			$ct = $_SERVER["CONTENT_TYPE"];
		} else {
			$ct = null;
		}
		$user = $this->server->getCurrentUserPrincipal();
		if ($user)
		{
			$uriNode = new Slash_ACL_FSExt_UriNode($user);
			$userUri = $uriNode->getUri();
			Slash_Debug::debug("ACL Plugin afterBind: content-type=$ct, current user uri=" . $userUri);
			$propreq["{DAV:}owner"] = new Slash_ACL_Property_Href($userUri);
			//TODO: should use the container ACL.
			$propreq["{DAV:}acl"] = $this->defaultAcl;
			if (isset($ct))
			{
				$propreq["{DAV:}getcontenttype"] = $ct;
			}
		}

		//$propreq["{DAV:}acl"] = $this->defaultAcl;
		$node = $this->server->tree->getNodeForPath($uri);
		$result = $node->updateProperties($propreq);
		// false is returned to stop the afterBind event
		return true;

	}

	/**
	 * This function handles the ACL HTTP method, which manipulates an ACL.
	 *
	 * @return void
	 */
	public function httpAcl()
	{

		// Due to unforgivable bugs in iCal, we're completely disabling MKCALENDAR support
		// for clients matching iCal in the user agent
		//$ua = $this->server->httpRequest->getHeader('User-Agent');
		//if (strpos($ua,'iCal/')!==false) {
		//throw new Sabre_DAV_Exception_Forbidden('ACL is not yet implemented.');
		//}
		$requestUri = $this->server->getRequestUri();
		$currentNode = $this->server->tree->getNodeForPath($requestUri);
		$propname = "{DAV:}acl";
		$propreq = array($propname);
		$props = $currentNode->getProperties($propreq);
		if (isset($props[$propname]))
		{
			$oldAclProp = $props[$propname];
		}
		$body = $this->server->httpRequest->getBody(true);
		$dom = Sabre_DAV_XMLUtil::loadDOMDocument($body);
		$newAcl = $dom->getElementsByTagNameNS("urn:DAV", "acl")->item(0);
		//Slash_Debug::debug("Plugin: handling ACL method; acl=" . Sabre_DAV_XMLUtil::toClarkNotation($acl));
		$newAclProp = Slash_ACL_Property_Acl::unserialize($newAcl);
		Slash_Debug::debug("Plugin: unserialized ACL");
		$props = array("{DAV:}acl"=>$newAclProp);

		//Check there's no conflict with the existing ACL.
		if (isset($oldAclProp))
		{
			$str = $oldAclProp->conflictsWith($newAclProp);
			if ($str)
			{
				//$this->server->httpResponse->sendStatus(409);
				throw new Sabre_DAV_Exception_Conflict($str);
			}
		}

		//Check that no invalid or abstract privilege is being granted.
		$aces = $newAclProp->getAces();
		foreach ($aces as $princStr=>$newAces)
		{
			foreach ($newAces as $ace)
			{
				foreach ($ace["privs"] as $acepriv)
				{
					$priv = $this->findPrivilege($this->privileges, $acepriv);
					if (!isset($priv))
					{
						//$this->server->httpResponse->sendStatus(403);
						throw new Sabre_DAV_Exception_Forbidden("Unknown privilege: $acepriv");
					}
					if ($priv["abstract"])
					{
						//$this->server->httpResponse->sendStatus(403);
						throw new Sabre_DAV_Exception_Forbidden("Cannot assign abstract privilege $acepriv");
					}
				}
			}
		}
			
			
		if ($currentNode->updateProperties($props))
		{
			Slash_Debug::debug("Plugin: ACL stored in node properties");
			$this->server->httpResponse->sendStatus(200);
		} else {
			Slash_Debug::debug("Plugin: Could not store ACL in node properties");
			$this->server->httpResponse->sendStatus(501);
		}
		$this->server->httpResponse->setHeader('Content-Length',0);
	}

	/*
	 * Return a flat array of the non-abstract supported privs.
	 */
	private function getSupportedPrivs($container, $privs)
	{
		foreach ($container as $privname=>$inner)
		{
			if (!$inner["abstract"])
			{
				$privs[] = $privname;
				if (isset($inner["container"]))
				{
					$this->getSupportedPrivs($inner["container"], &$privs);
				}
			}
		}
	}

	private function getCups($node)
	{
		$result = array();
		Slash_Debug::debug("ACL Plugin: getCups for node ". $node->getName());

		$user = $this->authBackend->getCurrentUser();
		if ($user)
		{
			//Slash_Debug::debug("ACL Plugin beforeMethod: got a user: " . $user["uri"]);
			Slash_Debug::debug("ACL Plugin: getCups for user " . $user["uri"]);
			$userPrinc = new Slash_ACL_Property_HrefPrincipal($user["uri"]);

			$propacl = "{DAV:}acl";
			$propreq = array($propacl);
			$props = $node->getProperties($propreq);
			if (isset($props[$propacl]))
			{
				$acl = $props[$propacl];
				//Go through all non-abstract supported privs, and see if the ACL grants each one.
				$privs = array();
				$this->getSupportedPrivs($this->privileges, &$privs);
				foreach ($privs as $priv)
				{
					if ($this->grantsPrivilege($acl, $node, $userPrinc, $priv))
					{
						Slash_Debug::debug("ACL Plugin: getCups: $priv is granted");
						$result[] = $priv;
					}
				}
			}
		}
		return new Slash_ACL_Property_CurrentUserPrivilegeSet($result);
	}

	/**
	 * afterGetProperties
	 *
	 * This method handler is invoked after properties for a specific resource
	 * are received. This allows us to add any properties that might have been
	 * missing.
	 *
	 * @param string $path
	 * @param array $properties
	 * @return void
	 */
	public function afterGetProperties($path, &$properties)
	{
		// Find out if we are currently looking at a principal resource
		Slash_Debug::debug("ACL Plugin: afterGetProperties for $path");
		$currentNode = $this->server->tree->getNodeForPath($path);
		$uriNode = new Slash_ACL_FSExt_UriNode($currentNode);
		$currentUri = $uriNode->getUri();
		Slash_Debug::debug("ACL_Plugin afterGetProperties: looking at a " . get_class($currentNode));
		$goodProps = $properties[200];
		foreach (array_keys($goodProps) as $propname)
		{
			switch ($propname)
			{
				case "{DAV:}acl":
					Slash_Debug::debugDump("ACL_Plugin afterGetProperties: propfindProps=", $this->propfindProps);
					if (!in_array($propname, $this->propfindProps))
					{
						unset($properties[200][$propname]);
					}
					break;
						
				case '{DAV:}supported-report-set':
					$properties[200]['{DAV:}supported-report-set']->addReport(array(
            "{DAV:}expand-property",
						"{DAV:}acl-principal-prop-set",
						"{DAV:}principal-match",
						"{DAV:}principal-property-search",
						"{DAV:}principal-search-property-set"
					));
					break;
			}
		}
		$badProps = $properties[404];
		Slash_Debug::debugDump("ACL_Plugin afterGetProperties: got " . count($badProps), $badProps);
		foreach (array_keys($badProps) as $propname)
		{
			Slash_Debug::debug("ACL_Plugin afterGetProperties: 404 for $propname");
			switch ($propname)
			{
				case '{DAV:}group-membership':
					if ($currentNode instanceof Slash_ACL_FSExt_Principal)
					{
						//$princuri = $uriNode->getUri();
						$groups = array();
						$principals = $this->server->getAllPrincipals();
						//Go through all group Principals, and add each one that includes this Principal in the group-member-set.
						$propreq[] = "{DAV:}group-member-set";
						foreach($principals as $principal)
						{
							$props = $principal->getProperties(null);
							if (isset($props["{DAV:}group-member-set"]))
							{
								$gmsprop = $props["{DAV:}group-member-set"];
								$hrefs = $gmsprop->getHrefs();
								foreach($hrefs as $href)
								{
									//Slash_Debug::debug("ACL Plugin: afterGetProperties: type of href=".gettype($href));
									$uri = $href->getHref();
									//Slash_Debug::debug("ACL Plugin: afterGetProperties: compare $uri with $princuri");
									if ($uri == $currentUri)
									{
										$uriNode = new Slash_ACL_FSExt_UriNode($principal);
										$groups[] = new Slash_ACL_Property_Href($uriNode->getUri());
									}
								}
							}
						}
						$properties[200][$propname] = new Slash_ACL_Property_GroupMembership($groups);
						unset($properties[404][$propname]);
					}
					break;

				case '{DAV:}current-user-principal':
					if ($ui = $this->authBackend->getCurrentUser())
					{
						$properties[200][$propname] = new Slash_ACL_Property_HrefPrincipal($ui['uri']);
					} else {
						$properties[200][$propname] = new Slash_ACL_Property_UnauthenticatedPrincipal();
					}
					unset($properties[404][$propname]);
					break;
						
				case'{DAV:}principal-collection-set':
					$properties[200][$propname] = new Slash_ACL_Property_Href('principals');
					unset($properties[404][$propname]);
					break;

				case '{DAV:}supported-privilege-set' :
					$properties[200][$propname] =  new Slash_ACL_Property_SupportedPrivilegeSet($this->privileges);
					unset($properties[404][$propname]);
					break;
				case '{DAV:}resourcetype' :
					$properties[200][$propname] =  new Sabre_DAV_Property_ResourceType(array('{DAV:}principal','{DAV:}collection'));
					unset($properties[404][$propname]);
					break;
				case '{DAV:}getcontenttype' :
					//$fi=finfo_open(FILEINFO_MIME_TYPE);
					//$rootNode = $this->server->tree->getNodeForPath("/");
					//$uriNode = new Slash_ACL_FSExt_UriNode($rootNode);
					$fn = Slash_CalDAV_Server::getRootDirectory() . "/" . $path;
					Slash_Debug::debug("ACL Plugin: afterGetProperties: path for node is $fn");
					$properties[200][$propname] =  mime_content_type($fn);
					unset($properties[404][$propname]);
					break;
				case '{DAV:}owner' :
					$href = new Slash_ACL_Property_Href("/principals/admin");
					$properties[200][$propname] =  $href;
					unset($properties[404][$propname]);
					break;
				case '{DAV:}principal-URL' :
					$properties[200][$propname] = new Slash_ACL_Property_Href($currentUri);
					unset($properties[404][$propname]);
					break;
				case '{DAV:}alternate-URI-set' :
					$properties[200][$propname] = array();
					unset($properties[404][$propname]);
					break;
				case '{DAV:}acl-restrictions' :
					$properties[200][$propname] = $this->aclRestrictions;
					unset($properties[404][$propname]);
					break;
				case '{DAV:}current-user-privilege-set' :
					$properties[200][$propname] = $this->getCups($currentNode);
					unset($properties[404][$propname]);
					break;
			}
		}
		Slash_Debug::debug("ACL_Plugin afterGetProperties: done");
	}


	/*
	 * Get the ACL for the current node.
	 * For each principal listed in the ACL,
	 * 	get the properties requested
	 *  generate a <response> element.
	 */
	public function principalPropSetReport($dom)
	{
		Slash_Debug::debugDump("ACL_Plugin principalPropSetReport: dom ", $dom);
			
		$httpDepth = $this->server->getHTTPDepth(0);
		if ($httpDepth!==0)
		{
			throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0');
		}

		$reqPrincs = array();

		$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild));
		$propertyList = array();
		$requestUri = $this->server->getRequestUri();
		$currentNode = $this->server->tree->getNodeForPath($requestUri);
		$propname = "{DAV:}acl";
		$propreq = array($propname);
		$props = $currentNode->getProperties($propreq);
		if (isset($props[$propname]))
		{
			$aces = $props[$propname]->getAces();
			$acePrincs = array_unique(array_keys($aces));
			foreach ($acePrincs as $princStr)
			{
				$princ = Slash_ACL_Property_Principal::fromString($princStr);
				if ($princ instanceof Slash_ACL_Property_PropertyPrincipal)
				{
					$reqPrincs[] = $princ->getHrefForNode($currentNode);
				} else if ($princ instanceof Slash_ACL_Property_HrefPrincipal)
				{
					$reqPrincs[] = $princ->getHref();
				}
			}
			$reqPrincs = array_unique($reqPrincs);
			$baseUri = $this->server->getBaseUri();
			foreach ($reqPrincs as $reqPrinc)
			{
				if (strpos($reqPrinc, $baseUri) === 0)
				{
					$reqPrinc = substr($reqPrinc, strlen($baseUri));
				}
				$reqPrincResource = $this->server->tree->getNodeForPath($reqPrinc);
				$props = $reqPrincResource->getProperties($properties);
				Slash_Debug::debugDump("ACL_Plugin: pps report for $reqPrinc: props:", $props);
				$propertyList[] = array("href"=>$reqPrinc, 200=>$props);
			}
			$this->server->httpResponse->sendStatus(207);
			$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
			$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
		}

	}


	/*
	 * For each resource in the tree rooted at the current node
	 *  	Get the property (a principal) from the resource
	 *    If the property matches the current user
	 *  		generate a <response> element for the resource
	 *  		Add any requested properties to the response
	 *
	 */
	private function principalMatchProp($principal, $node, $propName, $properties, &$propertyList)
	{
		$pnprops = $node->getProperties(array($propName));
		if (isset($pnprops[$propName]))
		{
			$prop = $pnprops[$propName];
			//$prop is the named property on the node, e.g. the owner.
			if ($prop instanceof Sabre_DAV_Property_IHref)
			{
				$uriNode = new Slash_ACL_FSExt_UriNode($node);
				$nodeUri = $uriNode->getUri();
				$href = $prop->getHref();
				Slash_Debug::debug("ACL_Plugin PrincipalMatchProp: node $nodeUri has an Href property $propName with value $href");
				$propPrinc = new Slash_ACL_Property_HrefPrincipal($href);
				if ($propPrinc->includesPrincipal($principal, $node))
				{
					/*
					 * If we're here, it means the node had the named prop, and
					 * its value was an href principal that includes this user.
					 */
					Slash_Debug::debug("ACL_Plugin PrincipalMatchProp: Principal $href includes " . $principal->getHref());
					$props = $this->server->getPropertiesForPath($nodeUri, $properties);
					$propertyList[] = $props[0];
				}
			}
		}
		if ($node instanceof Slash_ACL_FSExt_Directory)
		{
			$children = $node->getChildren();
			foreach ($children as $childNode)
			{
				$this->principalMatchProp($principal, $childNode, $propName, $properties, $propertyList);
			}
		}
	}

	/*
	 * For each resource in the tree rooted at the current node
	 *	 	If the resource is a principal,
	 * 			If the principal includes the current user,
	 *  			generate a <response> element fore the principal
	 *  			Add any requested properties to the response
	 */
	private function principalMatchSelf($principal, $node, $properties, &$propertyList)
	{
		$uriNode = new Slash_ACL_FSExt_UriNode($node);
		$nodeUri = $uriNode->getUri();
		Slash_Debug::debug("ACL_Plugin principalMatchSelf: princ=" . $principal->toString() . ", node=$nodeUri");
		if ($node instanceof Slash_ACL_FSExt_Principal)
		{
			Slash_Debug::debug("ACL_Plugin principalMatchSelf: node is a principal");
			$princProp = new Slash_ACL_Property_HrefPrincipal($nodeUri);
			if ($princProp->includesPrincipal($principal, $node))
			{
				$princHref = $principal->getHref();
				Slash_Debug::debug("ACL_Plugin principalMatchSelf: node $nodeUri includes $princHref");
				$props = $this->server->getPropertiesForPath($nodeUri, $properties);
				Slash_Debug::debugDump("ACL_Plugin principalMatchSelf: serverProps:", $props);
				$propertyList[] = $props[0];
			}
		}
		if ($node instanceof Slash_ACL_FSExt_Directory)
		{
			$children = $node->getChildren();
			foreach ($children as $childNode)
			{
				$this->principalMatchSelf($principal, $childNode, $properties, &$propertyList);
			}
		}
	}


	/*
	 * For each resource in the tree rooted at the current node
	 *  If it's a "self" report
	 *	 	If the resource is a principal,
	 * 			If the principal includes the current user,
	 *  			generate a <response> element fore the principal
	 *  			Add any requested properties to the response
	 *  Else it's a "principal-property" report:
	 *  	Get the property from the resource
	 *    If the property matches the current user
	 *  		generate a <response> element for the resource
	 *  		Add any requested properties to the response
	 *
	 */
	public function principalMatchReport($dom)
	{
		Slash_Debug::debugDom("ACL_Plugin principalMatchReport: dom ", $dom);
			
		$httpDepth = $this->server->getHTTPDepth(0);
		if ($httpDepth!==0)
		{
			throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0');
		}

		$requestUri = $this->server->getRequestUri();
		$currentNode = $this->server->tree->getNodeForPath($requestUri);
		$properties = array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild, $this->server->propertyMap));
		Slash_Debug::debugDump("ACL_Plugin principalMatchReport: properties=", $properties);

		$propertyList = array();
		$nlPrincProp = $dom->getElementsByTagNameNS("urn:DAV", "principal-property");
		if ($nlPrincProp->length > 0)
		{
			$princProp = $nlPrincProp->item(0);
			Slash_Debug::debugDom("ACL_Plugin principalMatchReport: got a principal-property:", $princProp);
			$propName = Sabre_DAV_XMLUtil::toClarkNotation($princProp->getElementsByTagName("*")->item(0));
		}
			
		//$authPlugin = $this->server->getPlugin("Slash_DAV_Auth_Plugin");
		$user = $this->authBackend->getCurrentUser();
		if ($user)
		{
			$userPrinc = new Slash_ACL_Property_HrefPrincipal($user["uri"]);
		} else {
			$userPrinc = new Slash_ACL_Property_UnauthenticatedPrincipal();
		}

		$isSelf = ($dom->getElementsByTagNameNS("urn:DAV", "self")->length > 0);
		if ($isSelf)
		{
			$this->principalMatchSelf($userPrinc, $currentNode, $properties, &$propertyList);
		} else if (isset($propName))
		{
			$this->principalMatchProp($userPrinc, $currentNode, $propName, $properties, &$propertyList);
		} else {
			throw new Sabre_DAV_Exception_BadRequest("The report request contains neither a DAV:self nor a DAV:principal-property element");
		}
		$this->server->httpResponse->sendStatus(207);
		$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
		$this->server->httpResponse->sendBody($this->server->generateMultiStatus($propertyList));
	}

	/**
	 * The expand-property report is defined in RFC3253 section 3-8.
	 *
	 * This report is very similar to a standard PROPFIND. The difference is
	 * that it has the additional ability to look at properties containing a
	 * {DAV:}href element, follow that property and grab additional elements
	 * there.
	 *
	 * Other rfc's, such as ACL rely on this report, so it made sense to put
	 * it in this plugin.
	 *
	 * @param DOMElement $dom
	 * @return void
	 */
	protected function expandPropertyReport($dom)
	{
		$requestedProperties = $this->parseExpandPropertyReportRequest($dom->firstChild->firstChild);
		$depth = $this->server->getHTTPDepth(0);
		$requestUri = $this->server->getRequestUri();

		$result = $this->expandProperties($requestUri,$requestedProperties,$depth);

		$dom = new DOMDocument('1.0','utf-8');
		$dom->formatOutput = true;
		$multiStatus = $dom->createElement('d:multistatus');
		$dom->appendChild($multiStatus);

		// Adding in default namespaces
		foreach($this->server->xmlNamespaces as $namespace=>$prefix)
		{
			$multiStatus->setAttribute('xmlns:' . $prefix,$namespace);
		}

		foreach($result as $entry)
		{
			$entry->serialize($this->server,$multiStatus);
		}

		$xml = $dom->saveXML();
		$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
		$this->server->httpResponse->sendStatus(207);
		$this->server->httpResponse->sendBody($xml);

		// Make sure the event chain is broken
		return false;

	}

	/**
	 * This method is used by expandPropertyReport to parse
	 * out the entire HTTP request.
	 *
	 * @param DOMElement $node
	 * @return array
	 */
	protected function parseExpandPropertyReportRequest($node)
	{
		$requestedProperties = array();
		do
		{
			if (Sabre_DAV_XMLUtil::toClarkNotation($node)!=='{DAV:}property') continue;

			if ($node->firstChild)
			{
				$children = $this->parseExpandPropertyReportRequest($node->firstChild);
			} else {
				$children = array();
			}

			$namespace = $node->getAttribute('namespace');
			if (!$namespace)
			{
				$namespace = 'DAV:';
			}

			$propName = '{'.$namespace.'}' . $node->getAttribute('name');
			$requestedProperties[$propName] = $children;

		} while ($node = $node->nextSibling);
		return $requestedProperties;
	}

	/**
	 * This method expands all the properties and returns
	 * a list with property values
	 *
	 * @param array $path
	 * @param array $requestedProperties the list of required properties
	 * @param array $depth
	 */
	protected function expandProperties($path,array $requestedProperties,$depth)
	{
		$foundProperties = $this->server->getPropertiesForPath($path,array_keys($requestedProperties),$depth);

		$result = array();

		foreach($foundProperties as $node)
		{
			foreach($requestedProperties as $propertyName=>$childRequestedProperties)
			{
				// We're only traversing if sub-properties were requested
				if(count($childRequestedProperties)===0)
				{
					continue;
				}

				// We only have to do the expansion if the property was found
				// and it contains an href element.
				if (!array_key_exists($propertyName,$node[200]))
				{
					continue;
				}
				if (!($node[200][$propertyName] instanceof Sabre_DAV_Property_IHref))
				{
					continue;
				}
				$href = $node[200][$propertyName]->getHref();
				list($node[200][$propertyName]) = $this->expandProperties($href,$childRequestedProperties,0);
			}
			$result[] = new Sabre_DAV_Property_Response($path, $node);
		}
		return $result;
	}

	protected function principalSearchPropertySetReport(DOMDocument $dom)
	{
		$searchProperties = array('{DAV:}displayname' => 'display name');
		$httpDepth = $this->server->getHTTPDepth(0);
		if ($httpDepth!==0)
		{
			throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0');
		}

		if ($dom->firstChild->hasChildNodes())
		{
			throw new Sabre_DAV_Exception_BadRequest('The principal-search-property-set report element is not allowed to have child elements');
		}
		$dom = new DOMDocument('1.0','utf-8');
		$dom->formatOutput = true;
		$root = $dom->createElement('d:principal-search-property-set');
		$dom->appendChild($root);
		// Adding in default namespaces
		foreach($this->server->xmlNamespaces as $namespace=>$prefix)
		{
			$root->setAttribute('xmlns:' . $prefix,$namespace);
		}

		$nsList = $this->server->xmlNamespaces;
		foreach($searchProperties as $propertyName=>$description)
		{
			$psp = $dom->createElement('d:principal-search-property');
			$root->appendChild($psp);

			$prop = $dom->createElement('d:prop');
			$psp->appendChild($prop);

			$propName = null;
			preg_match('/^{([^}]*)}(.*)$/',$propertyName,$propName);


			// If the namespace was defined in the top-level xml namespaces, it means
			// there was already a namespace declaration, and we don't have to worry about it.
			//if (isset($server->xmlNamespaces[$propName[1]])) {
			$currentProperty = $dom->createElement($nsList[$propName[1]] . ':' . $propName[2]);
			$prop->appendChild($currentProperty);

			$descriptionElem = $dom->createElement('d:description');
			$descriptionElem->setAttribute('xml:lang','en');
			$descriptionElem->appendChild($dom->createTextNode($description));
			$psp->appendChild($descriptionElem);
		}

		$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
		$this->server->httpResponse->sendStatus(200);
		$this->server->httpResponse->sendBody($dom->saveXML());
	}

	protected function principalPropertySearchReport($dom)
	{
		$searchableProperties = array('{DAV:}displayname' => 'display name');
		list($searchProperties, $requestedProperties) = $this->parsePrincipalPropertySearchReportRequest($dom);
		$uri = $this->server->getRequestUri();

		$result = array();

		$lookupResults = $this->server->getPropertiesForPath($uri, array_keys($searchProperties), 1);

		// The first item in the results is the parent, so we get rid of it.
		array_shift($lookupResults);

		$matches = array();

		foreach($lookupResults as $lookupResult)
		{
			foreach($searchProperties as $searchProperty=>$searchValue)
			{
				if (!isset($searchableProperties[$searchProperty]))
				{
					throw new Sabre_DAV_Exception_BadRequest('Searching for ' . $searchProperty . ' is not supported');
				}

				if (isset($lookupResult[200][$searchProperty]) &&
				mb_stripos($lookupResult[200][$searchProperty], $searchValue, 0, 'UTF-8')!==false)
				{
					$matches[] = $lookupResult['href'];
				}
			}
		}

		$matchProperties = array();

		foreach($matches as $match)
		{
			list($result) = $this->server->getPropertiesForPath($match, $requestedProperties, 0);
			$matchProperties[] = $result;
		}

		$xml = $this->server->generateMultiStatus($matchProperties);
		$this->server->httpResponse->setHeader('Content-Type','application/xml; charset=utf-8');
		$this->server->httpResponse->sendStatus(207);
		$this->server->httpResponse->sendBody($xml);
	}

	protected function parsePrincipalPropertySearchReportRequest($dom)
	{

		$httpDepth = $this->server->getHTTPDepth(0);
		if ($httpDepth!==0)
		{
			throw new Sabre_DAV_Exception_BadRequest('This report is only defined when Depth: 0');
		}

		$searchProperties = array();

		// Parsing the search request
		foreach($dom->firstChild->childNodes as $searchNode)
		{
			if (Sabre_DAV_XMLUtil::toClarkNotation($searchNode)!=='{DAV:}property-search')
			{
				continue;
			}

			$propertyName = null;
			$propertyValue = null;

			foreach($searchNode->childNodes as $childNode)
			{
				switch(Sabre_DAV_XMLUtil::toClarkNotation($childNode))
				{
					case '{DAV:}prop' :
						$property = Sabre_DAV_XMLUtil::parseProperties($searchNode);
						reset($property);
						$propertyName = key($property);
						break;

					case '{DAV:}match' :
						$propertyValue = $childNode->textContent;
						break;
				}
			}

			if (is_null($propertyName) || is_null($propertyValue))
			{
				throw new Sabre_DAV_Exception_BadRequest('Invalid search request. propertyname: ' . $propertyName . '. propertvvalue: ' . $propertyValue);
			}
			$searchProperties[$propertyName] = $propertyValue;
		}
		return array($searchProperties, array_keys(Sabre_DAV_XMLUtil::parseProperties($dom->firstChild)));
	}
}
